#include "FileContentDisk.h"
#include "TestCasesHelper.h"
#include "globals.h"
#include <wx/utils.h>
#include <wx/progdlg.h>
#include <algorithm>
#include <memory>
#include <logmanager.h>

#define my_assertimpl( expr, exprStr, file, line ) \
  do \
    if ( !( expr ) ) { \
      LogManager::Get()->DebugLog( F( _T("HEXEDIT Assertion failed, file: %s, line: %d"), file, line ) ); \
      LogManager::Get()->DebugLog( F( _T(" EXPR: %s"), exprStr ) ); \
      LogManager::Get()->DebugLog( F( _T(" Block1: %d:%d"), (int)b1->start, (int)b1->size ) ); \
      LogManager::Get()->DebugLog( F( _T(" Block2: %d:%d"), (int)b2->start, (int)b2->size ) ); \
    } \
  while ( false )
#define my_assert( expr ) assert( expr )

class FileContentDisk::DiskModificationData: public FileContentBase::ModificationData {
  public:
    typedef FileContentBase::OffsetT OffsetT;
    DiskModificationData( FileContentDisk* fcd, OffsetT start ) : m_Fcd( fcd ), m_Start( start ) {}
    virtual void Apply() {
      m_Fcd->SetBlock( &m_DataAfter[0], m_Start, m_DataBefore.size(), m_DataAfter.size() );
    }
    virtual void Revert() {
      m_Fcd->SetBlock( &m_DataBefore[0], m_Start, m_DataAfter.size(), m_DataBefore.size() );
    }
    virtual OffsetT Length() {
      return wxMax( m_DataBefore.size(), m_DataAfter.size() );
    }
    inline std::vector< char >& GetDataBefore() {
      return m_DataBefore;
    }
    inline std::vector< char >& GetDataAfter()  {
      return m_DataAfter;
    }

  private:
    FileContentDisk* m_Fcd;
    OffsetT m_Start;
    std::vector< char > m_DataBefore;
    std::vector< char > m_DataAfter;
};


FileContentDisk::FileContentDisk(): m_TestMode( false ) {
}

FileContentDisk::~FileContentDisk() {
  ClearBlocks();
}

void FileContentDisk::ClearBlocks() {
  for( size_t i = 0; i < m_Contents.size(); ++i ) {
    delete m_Contents[ i ];
  }
  m_Contents.clear();
}

bool FileContentDisk::ReadFile( const wxString& fileName ) {
  m_File.Open( fileName, wxFile::read_write );
  if( !m_File.IsOpened() ) {
    return false;
  }
  ResetBlocks();
  m_FileName = fileName;
  return true;
}

bool FileContentDisk::WriteFile( const wxString& fileName ) {
  if( fileName != m_FileName ) {
    if( WriteToDifferentFile( fileName ) ) {
      UndoNotifySaved();
      return true;
    }
    return false;
  }
  bool noShiftedDiskBlocks = true;
  if( GetSize() < ( OffsetT )m_File.Length() ) {
    noShiftedDiskBlocks = false;
  } else {
    for( size_t i = 0; i < m_Contents.size(); ++i ) {
      DataBlock* block = m_Contents[i];
      if( !block->IsFromDisk() ) {
        continue;
      }
      if( block->fileStart != block->start ) {
        noShiftedDiskBlocks = false;
        break;
      }
    }
  }
  if( noShiftedDiskBlocks ) {
    if( WriteFileEasiest() ) {
      UndoNotifySaved();
      return true;
    }
    return false;
  }
  bool noExtraFilesNeeded = false;
  if( GetSize() < ( OffsetT )m_File.Length() ) {
    noShiftedDiskBlocks = false;
  }
  if( noExtraFilesNeeded ) {
    if( WriteFileOnDisk() ) {
      UndoNotifySaved();
      return true;
    }
    return false;
  }
  wxLongLong size = GetSize();
  wxLongLong diskFree;
  if( !wxGetDiskSpace( wxPathOnly( fileName ), 0, &diskFree ) ) {
    if( msging( _( "查询磁盘可用空间时出错.\n 这可能会导致保存失败是否仍要保存文件?" ),
                _( "查询可用空间时出错" ), wxYES_NO ) != wxID_YES ) {
      return false;
    }
  }
  if( diskFree < size + 128 * 1024 ) {
    if( noExtraFilesNeeded ) {
      if( msging( _( "驱动器上没有足够的可用空间，无法使用安全方法保存文件.\n"
                     "我们仍然可以尝试保存文件，但保存过程中的任何电源或系统故障都会损坏文件是否要使用不安全的方法？" ),
                  _( "可用空间不足" ),
                  wxYES_NO ) != wxID_YES ) {
        return false;
      }
      if( WriteFileOnDisk() ) {
        UndoNotifySaved();
        return true;
      }
      return false;
    } else {
      msging( _( "驱动器上没有足够的可用空间来保存更改.\n"
                 "请释放一些空间并重试" ),
              _( "可用空间不足" ),
              wxOK );
      return false;
    }
  }
  if( size > 16 * 1024 * 1024 ) {
    if( msging(
          _( "HexEdit: 保存可能需要很长时间" ),
          _( "保存文件可能需要很长时间.\n"
             "你想继续吗？\n" ), wxYES_NO ) != wxID_YES ) {
      return false;
    }
  }
  if( WriteFileTemporary() ) {
    UndoNotifySaved();
    return true;
  }
  return false;
}

FileContentDisk::OffsetT FileContentDisk::GetSize() {
  return m_Contents.back()->start + m_Contents.back()->size;
}

FileContentDisk::OffsetT FileContentDisk::Read( void* buff, OffsetT position, OffsetT length ) {
  OffsetT read = 0;
  for( size_t i = FindBlock( position ); length && i < m_Contents.size(); ++i ) {
    DataBlock* block = m_Contents[i];
    OffsetT blockShift = position - block->start;
    OffsetT readFromThisBlock = wxMin( length, block->size - blockShift );
    if( block->IsFromDisk() ) {
      m_File.Seek( block->fileStart + blockShift );
      m_File.Read( buff, readFromThisBlock );
    } else
    { memcpy( buff, &block->data[ blockShift ], readFromThisBlock ); }
    position += readFromThisBlock;
    length -= readFromThisBlock;
    read += readFromThisBlock;
    buff = ( char* )buff + readFromThisBlock;
  }
  return read;
}

FileContentDisk::ModificationData * FileContentDisk::BuildChangeModification( OffsetT position, OffsetT length, const void* data ) {
  assert( length > 0 );
  DiskModificationData* modData = new DiskModificationData( this, position );
  std::vector< char >& before = modData->GetDataBefore();
  before.resize( length );
  Read( &before[ 0 ], position, length );
  std::vector< char >& after = modData->GetDataAfter();
  after.resize( length );
  if( data ) {
    std::copy( ( char* )data, ( char* )data + length, after.begin() );
  }
  return modData;
}

FileContentDisk::ModificationData* FileContentDisk::BuildAddModification( OffsetT position, OffsetT length, const void* data ) {
  assert( length > 0 );
  DiskModificationData* modData = new DiskModificationData( this, position );
  std::vector< char >& after = modData->GetDataAfter();
  after.resize( length );
  if( data ) {
    std::copy( ( char* )data, ( char* )data + length, after.begin() );
  }
  return modData;
}

FileContentDisk::ModificationData * FileContentDisk::BuildRemoveModification( OffsetT position, OffsetT length ) {
  assert( length > 0 );
  DiskModificationData* modData = new DiskModificationData( this, position );
  std::vector< char >& before = modData->GetDataBefore();
  before.resize( length );
  Read( &before[ 0 ], position, length );
  return modData;
}


void FileContentDisk::SetBlock( const char* data, OffsetT pos, OffsetT lengthBefore, OffsetT lengthAfter ) {
  OffsetT collectiveLength = wxMin( lengthBefore, lengthAfter );
  lengthBefore -= collectiveLength;
  lengthAfter  -= collectiveLength;
  size_t i = FindBlock( pos );
  for( ; collectiveLength && i < m_Contents.size(); ++i ) {
    DataBlock* block = m_Contents[ i ];
    OffsetT blockPos = pos - block->start;
    OffsetT blockLen = wxMin( collectiveLength, block->size - blockPos );
    if( block->IsFromDisk() ) {
      if( blockPos ) {
        block = InsertNewBlock( i++, blockPos );
        blockPos = 0;
        ConsistencyCheck();
      }
      if( block->size > blockLen ) {
        InsertNewBlock( i, blockLen );
        ConsistencyCheck();
      }
      assert( block->size == blockLen );
      assert( block->start == pos );
      block->data.resize( blockLen );
    }
    memcpy( &block->data[ blockPos ], data, blockLen );
    pos += blockLen;
    collectiveLength -= blockLen;
    data += blockLen;
  }
  MergeBlocks( i - 1 );
  ConsistencyCheck();
  assert( !( lengthBefore && lengthAfter ) );
  if( lengthBefore ) {
    i = FindBlock( pos );
    OffsetT positionShift = lengthBefore;
    for( ; lengthBefore && i < m_Contents.size(); ) {
      DataBlock* block = m_Contents[ i ];
      OffsetT blockPos = pos - block->start;
      OffsetT blockLen = wxMin( lengthBefore, block->size - blockPos );
      if( blockPos + blockLen < block->size ) {
        DataBlock* newBlock = InsertNewBlock( i, blockPos + blockLen );
        if( !block->IsFromDisk() ) {
          newBlock->data.insert(
            newBlock->data.end(),
            block->data.begin() + block->size,
            block->data.end() );
          block->data.resize( block->size );
        }
      }
      if( blockPos ) {
        DataBlock* newBlock = InsertNewBlock( i++, blockPos );
        if( !block->IsFromDisk() ) {
          block->data.resize( block->size );
        }
        block = newBlock;
      }
      assert( block->start == pos );
      assert( block->size == blockLen );
      delete m_Contents[ i ];
      m_Contents.erase( m_Contents.begin() + i );
      pos += blockLen;
      lengthBefore -= blockLen;
    }
    while( i < m_Contents.size() ) {
      m_Contents[ i++ ]->start -= positionShift;
    }
  }
  if( lengthAfter ) {
    i = FindBlock( pos );
    if( i == m_Contents.size() ) {
      DataBlock* last = m_Contents.back();
      DataBlock* newBlock = new DataBlock;
      newBlock->start = last->start + last->size;
      newBlock->size  = lengthAfter;
      newBlock->data.resize( lengthAfter );
      memcpy( &newBlock->data[0], data, lengthAfter );
    } else {
      OffsetT positionShift = lengthAfter;
      DataBlock* block = m_Contents[ i ];
      OffsetT blockPos = pos - block->start;
      if( block->IsFromDisk() ) {
        assert( blockPos < block->size );
        InsertNewBlock( i, blockPos );
        if( blockPos ) {
          block = InsertNewBlock( i++, blockPos );
          blockPos = 0;
        }
      }
      block->data.insert( block->data.begin() + blockPos, data, data + lengthAfter );
      block->size += lengthAfter;
      while( ++i < m_Contents.size() ) {
        m_Contents[ i ]->start += positionShift;
      }
    }
  }
  ConsistencyCheck();
}

FileContentDisk::DataBlock* FileContentDisk::InsertNewBlock( size_t blockIndex, OffsetT position ) {
  DataBlock* block = m_Contents[ blockIndex ];
  assert( position <= block->size );
  DataBlock* newBlock = new DataBlock;
  newBlock->start = block->start + position;
  newBlock->fileStart = block->fileStart + position;
  newBlock->size = block->size - position;
  block->size = position;
  m_Contents.insert( m_Contents.begin() + blockIndex + 1, newBlock );
  return newBlock;
}


size_t FileContentDisk::FindBlock( OffsetT offset ) {
  struct cmp {
    static inline bool f( OffsetT offset, const DataBlock* block1 ) {
      return block1->start > offset;
    }
  };
  ConsistencyCheck();
  std::vector< DataBlock* >::iterator it = std::upper_bound( m_Contents.begin(), m_Contents.end(), offset, &cmp::f );
  assert( it != m_Contents.begin() );
  --it;
  if( ( *it )->start + ( *it )->size <= offset ) {
    return m_Contents.size();
  }
  return it - m_Contents.begin();
}

void FileContentDisk::ConsistencyCheck() {
  assert( !m_Contents.empty() );
  for( size_t i = 1; i < m_Contents.size(); ++i ) {
    DataBlock* b1 = m_Contents[ i - 1 ];
    DataBlock* b2 = m_Contents[ i ];
    my_assert( b1->size );
    my_assert( b2->size );
    my_assert( b1->start + b1->size == b2->start );
    my_assert( b1->IsFromDisk() || ( b1->size == b1->data.size() ) );
    my_assert( b2->IsFromDisk() || ( b2->size == b2->data.size() ) );
  }
}

void FileContentDisk::MergeBlocks( size_t startPosition ) {
  startPosition = wxMin( startPosition, m_Contents.size() - 1 );
  bool isFromDisk = m_Contents[ startPosition ]->IsFromDisk();
  size_t firstMergedBlock = startPosition;
  while( firstMergedBlock > 0 ) {
    DataBlock* block = m_Contents[ firstMergedBlock - 1 ];
    if( isFromDisk ) {
      if( !block->IsFromDisk() ) {
        break;
      }
      if( block->fileStart + block->size != m_Contents[ firstMergedBlock ]->fileStart ) {
        break;
      }
    } else {
      if( block->IsFromDisk() ) {
        break;
      }
    }
    firstMergedBlock--;
  }
  size_t lastMergedBlock = startPosition;
  while( lastMergedBlock < m_Contents.size() - 1 ) {
    DataBlock* block     = m_Contents[ lastMergedBlock + 1 ];
    DataBlock* blockPrev = m_Contents[ lastMergedBlock ];
    if( isFromDisk ) {
      if( !block->IsFromDisk() ) {
        break;
      }
      if( blockPrev->fileStart + blockPrev->size != block->fileStart ) {
        break;
      }
    } else {
      if( block->IsFromDisk() ) {
        break;
      }
    }
    lastMergedBlock++;
  }
  if( firstMergedBlock == lastMergedBlock ) {
    return;
  }
  DataBlock* block = m_Contents[ firstMergedBlock ];
  if( !isFromDisk ) {
    size_t size = 0;
    for( size_t i = firstMergedBlock; i <= lastMergedBlock; ++i ) {
      size += m_Contents[ i ]->size;
    }
    block->data.reserve( size );
  }
  for( size_t i = firstMergedBlock + 1; i <= lastMergedBlock; ++i ) {
    DataBlock* nextBlock = m_Contents[ i ];
    if( isFromDisk ) {
      assert( block->fileStart + block->size == nextBlock->fileStart );
    } else
    { block->data.insert( block->data.end(), nextBlock->data.begin(), nextBlock->data.end() ); }
    block->size += nextBlock->size;
    delete nextBlock;
  }
  m_Contents.erase( m_Contents.begin() + firstMergedBlock + 1, m_Contents.begin() + lastMergedBlock + 1 );
}

bool FileContentDisk::WriteFileEasiest() {
  static const int maxProgress = 10000;
  std::unique_ptr< wxProgressDialog > dlg(
    m_TestMode ? 0 : new wxProgressDialog( _( "保存文件" ), _( "请稍候，正在保存文件..." ), maxProgress,
        Manager::Get()->GetAppWindow(),
        wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME ) );
  if( dlg.get() ) {
    dlg->Update( 0 );
  }
  DataBlock* newBlock = new DataBlock;
  newBlock->start = 0;
  newBlock->fileStart = 0;
  newBlock->size = 0;
  OffsetT totalSize = 0;
  for( size_t i = 0; i < m_Contents.size(); ++i ) {
    if( !m_Contents[ i ]->IsFromDisk() ) {
      totalSize += m_Contents[ i ]->size;
    }
  }
  OffsetT totalWritten = 0;
  for( size_t i = 0; i < m_Contents.size(); ++i ) {
    DataBlock* block = m_Contents[ i ];
    if( !block->IsFromDisk() ) {
      m_File.Seek( block->start );
      size_t pos = 0;
      for( OffsetT left = block->size; left > 0; ) {
        OffsetT thisSize = wxMin( left, 1024 * 1024 );
        if( m_File.Write( &block->data[pos], ( size_t )thisSize ) != thisSize ) {
          msging( _T( "保存数据时出错" ) );
          m_Contents.erase( m_Contents.begin(), m_Contents.begin() + i );
          m_Contents.insert( m_Contents.begin(), newBlock );
          return false;
        }
        left -= thisSize;
        pos  += thisSize;
        totalWritten += thisSize;
        if( dlg.get() ) {
          dlg->Update( ( int )( ( double )totalWritten / ( double )totalSize * ( double )maxProgress ) );
        }
      }
    }
    newBlock->size += block->size;
    delete block;
    m_Contents[ i ] = 0;
  }
  m_Contents.clear();
  m_Contents.push_back( newBlock );
  return true;
}


bool FileContentDisk::WriteFileOnDisk() {
  return false;
}

bool FileContentDisk::WriteFileTemporary() {
  wxString nameProposition = m_FileName + _T( ".cbTemp" );
  for( int i = 0; i < 1000 && wxFileExists( nameProposition ); ++i ) {
    nameProposition = wxString::Format( _T( "%s.cbTemp.%03d" ), m_FileName.c_str(), i );
  }
  if( wxFileExists( nameProposition ) ) {
    msging( _( "无法创建临时文件.\n"
               "任何临时名称提议都无效" ) );
    return false;
  }
  wxFile fl( nameProposition, wxFile::write );
  if( !fl.IsOpened() ) {
    msging( _( "无法创建临时文件.\n" ) );
    return false;
  }
  if( !WriteToFile( fl ) ) {
    fl.Close();
    wxRemoveFile( nameProposition );
    msging( _( "无法将数据写入临时文件" ) );
    return false;
  }
  m_File.Close();
  fl.Close();
  if( !wxRenameFile( nameProposition, m_FileName, true ) ) {
    msging( _( "无法用新文件替换旧文件" ) );
    return false;
  }
  if( !m_File.Open( m_FileName, wxFile::read_write ) ) {
    msging( _( "保存后无法重新打开文件" ) );
    return false;
  }
  ResetBlocks();
  return true;
}

bool FileContentDisk::WriteToDifferentFile( const wxString& fileName ) {
  wxFile fl( fileName, wxFile::write );
  if( !fl.IsOpened() ) {
    msging( _T( "无法创建输出文件" ) );
    return false;
  }
  if( !WriteToFile( fl ) ) {
    msging( _T( "保存到文件时出错" ) );
    return false;
  }
  fl.Close();
  m_FileName = fileName;
  if( !m_File.Open( m_FileName ) ) {
    msging( _T( "保存后无法重新打开文件" ) );
    return false;
  }
  ResetBlocks();
  return true;
}

bool FileContentDisk::WriteToFile( wxFile& file ) {
  static const int maxProgress = 10000;
  std::unique_ptr< wxProgressDialog > dlg(
    m_TestMode ? 0 : new wxProgressDialog( _( "保存文件" ), _( "请稍候，正在保存文件..." ), maxProgress,
        Manager::Get()->GetAppWindow(),
        wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME ) );
  if( dlg.get() ) {
    dlg->Update( 0 );
  }
  OffsetT totalSize = GetSize();
  OffsetT totalWritten = 0;
  for( size_t i = 0; i < m_Contents.size(); ++i ) {
    DataBlock* block = m_Contents[ i ];
    if( block->IsFromDisk() ) {
      m_File.Seek( block->fileStart );
      for( OffsetT left = block->size; left > 0; ) {
        OffsetT thisSize = wxMin( left, 128 * 1024 );
        char buff[ 128 * 1024 ];
        size_t read = m_File.Read( buff, ( size_t )thisSize );
        if( read != thisSize ) {
          msging( _T( "无法从原始文件读取数据" ) );
          return false;
        }
        if( file.Write( buff, read ) != read ) {
          msging( _T( "写入数据时出错" ) );
          return false;
        }
        left -= read;
        totalWritten += read;
        if( dlg.get() ) {
          dlg->Update( ( int )( ( double )totalWritten / ( double )totalSize * ( double )maxProgress ) );
        }
      }
    } else {
      size_t pos = 0;
      for( OffsetT left = block->size; left > 0; ) {
        OffsetT thisSize = wxMin( left, 1024 * 1024 );
        if( file.Write( &block->data[pos], ( size_t )thisSize ) != thisSize ) {
          msging( _( "写入数据时出错" ) );
          return false;
        }
        left -= thisSize;
        pos  += thisSize;
        totalWritten += thisSize;
        if( dlg.get() ) {
          dlg->Update( ( int )( ( double )totalWritten / ( double )totalSize * ( double )maxProgress ) );
        }
      }
    }
  }
  return true;
}

void FileContentDisk::ResetBlocks() {
  ClearBlocks();
  DataBlock* first = new DataBlock;
  first->start = 0;
  first->fileStart = 0;
  first->size = m_File.Length();
  m_Contents.push_back( first );
}

class FileContentDisk::TestData {
  public:

    TestData() {
      m_Content.m_TestMode = true;
      OpenTempFile();
    }

    ~TestData() {
      CloseTempFile();
    }

    void Reset( int initialSize = 1 ) {
      CloseTempFile();
      OpenTempFile( initialSize );
    }


    bool MirrorCheck() {
      char Buff[0x1000];
      if( m_ContentMirror.size() != m_Content.GetSize() ) {
        return false;
      }
      OffsetT pos = 0;
      for( size_t left = m_ContentMirror.size(); left > 0; ) {
        size_t thisBlock = wxMin( left, sizeof( Buff ) );
        if( m_Content.Read( Buff, pos, thisBlock ) != thisBlock ) {
          return false;
        }
        char* ptr1 = Buff;
        char* ptr2 = &m_ContentMirror[ pos ];
        if( memcmp( ptr1, ptr2, thisBlock ) ) {
          return false;
        }
        left -= thisBlock;
        pos  += thisBlock;
      }
      return true;
    }


    bool Write( OffsetT position, OffsetT length ) {
      std::vector< char > buff = TempBuff( ( int )length );
      if( m_Content.Write( ExtraUndoData(), &buff[0], position, length ) != length ) {
        return false;
      }
      for( size_t i = 0; i < buff.size(); ++i ) {
        if( position + i < m_ContentMirror.size() ) {
          m_ContentMirror[ position + i ] = buff [ i ];
        }
      }
      return MirrorCheck();
    }


    bool Remove( OffsetT position, OffsetT length ) {
      if( m_Content.Remove( ExtraUndoData(), position, length ) != length ) {
        return false;
      }
      if( position < m_ContentMirror.size() ) {
        m_ContentMirror.erase(
          m_ContentMirror.begin() + position,
          m_ContentMirror.begin() + wxMin( m_ContentMirror.size(), position + length ) );
      }
      return MirrorCheck();
    }


    bool Add( OffsetT position, OffsetT length ) {
      std::vector< char > buff = TempBuff( ( int )length );
      if( m_Content.Add( ExtraUndoData(), position, length, &buff[0] ) != length ) {
        return false;
      }
      if( position <= m_ContentMirror.size() ) {
        m_ContentMirror.insert( m_ContentMirror.begin() + position, buff.begin(), buff.end() );
      }
      return MirrorCheck();
    }


    bool Save() {
      m_Content.WriteFile( m_Content.m_FileName );
      return MirrorCheck();
    }

  protected:

    size_t Size() { return m_ContentMirror.size(); }
    void   ResetBlocks() { m_Content.ResetBlocks(); }

  private:

    std::vector< char > TempBuff( int length ) {
      std::vector< char > buff( length );
      for( size_t i = 0; i < buff.size(); ++i ) {
        buff[ i ] = ( char )( rand() );
      }
      return buff;
    }


    void OpenTempFile( int initialSize = 1 ) {
      m_Content.m_FileName = wxFileName::CreateTempFileName( wxEmptyString, &m_Content.m_File );
      std::vector< char > buff = TempBuff( initialSize );
      m_Content.m_File.Write( &buff[0], initialSize );
      m_Content.ResetBlocks();
      m_ContentMirror.clear();
      m_ContentMirror.swap( buff );
    }


    void CloseTempFile() {
      m_Content.m_File.Close();
      wxRemoveFile( m_Content.m_FileName );
    }


    FileContentDisk m_Content;
    std::vector< char > m_ContentMirror;
};

typedef TestCasesHelper< FileContentDisk::TestData > TestCases;
static TestCases testCases;

TestCasesBase & FileContentDisk::GetTests() {
  return testCases;
}

template<>
template<>
void TestCases::Test<1>() {
  Reset( 1024 );
  for( int i = 0; i < 1024; i++ ) {
    Ensure( Write( i, 1 ), _T( "写入一个字节" ) );
  }
}

template<>
template<>
void TestCases::Test<2>() {
  Reset( 1024 );
  for( int i = 0; i < 1024; i += 2 ) {
    Ensure( Write( i, 1 ), _T( "写一个字节，一个字节保持不变" ) );
  }
}


template<>
template<>
void TestCases::Test<3>() {
  Reset( 1024 );
  for( int i = 0; i < 1024; i++ ) {
    size_t pos = rand() % 1024;
    size_t size = rand() % ( 1024 - pos );
    Ensure( Write( pos, size ), _T( "写入随机数据块" ) );
  }
}


template<>
template<>
void TestCases::Test<4>() {
  Reset( 1024 * 1024 );
  Ensure( MirrorCheck(), _T( "从一开始就断了" ) );
  for( int i = 0; i < 128; i++ ) {
    switch( rand() % 10 ) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5: {
        size_t pos  = rand() % ( Size() );
        size_t size = rand() % ( Size() - pos );
        Ensure( Write( pos, size ), _T( "超过1MB初始文件大小的压力测试-写入" ) );
        break;
      }
      case 6:
      case 7: {
        size_t pos  = rand() % ( Size() );
        size_t size = 100;
        Ensure( Add( pos, size ), _T( "超过1MB初始文件大小的压力测试-添加" ) );
        break;
      }
      case 8:
      case 9: {
        size_t pos  = rand() % ( Size() - 100 );
        size_t size = 100;
        Ensure( Remove( pos, size ), _T( "超过1MB初始文件大小的压力测试-删除" ) );
        break;
      }
      default:
        break;
    }
  }
  Ensure( Save(), _T( "保存测试文件" ) );
}

template<>
template<>
void TestCases::Test<5>() {
  Reset( 1024 );
  for( int i = 0; i < 1024; i += 2 ) {
    Ensure( Write( i, 1 ), _T( "写入一个字节" ) );
  }
  Ensure( Save(), _T( "使用简单方法保存文件（chees布局）" ) );
}

template<>
template<>
void TestCases::Test<6>() {
  Reset( 1024 * 1024 );
  Ensure( Remove( 1024 * 1023, 1024 ), _T( "从1MB文件末尾删除1kB" ) );
  Ensure( Save(), _T( "删除部分后保存文件" ) );
  ResetBlocks();
  Ensure( MirrorCheck(), _T( "删除部分后保存文件并检查" ) );
}
